// Copyright 2010 Abiola Ibrahim <abiola89@gmail.com>. All rights reserved.
// This source code is governed by New BSD License
// http://www.opensource.org/licenses/bsd-license.php

//package with structs StringTokens and StringBuffer similar to StringTokenizer and StringBuffer in java.
package strutils

import (
	"strings"
	"fmt"
)
//StringBuffer type
type StringBuffer struct {
	str string
}
//Creates an instance of StringBuffer
func NewStringBuffer(s string) *StringBuffer {
	sBuffer := new(StringBuffer)
	sBuffer.str = s
	return sBuffer
}
//Retrieves the string Content
func (this *StringBuffer) String() string {
	return this.str
}
//Appends string to the end of the string content
func (this *StringBuffer) AppendString(s string) {
	this.str = this.str + s
}
//Appends string value of the data to the end of the string content
func (this *StringBuffer) Append(data interface{}) {
	this.str = this.str + fmt.Sprint(data)
}
//inserts string value of the data to the specified index
func (this *StringBuffer) Insert(index int, data interface{}) {
	part1 := this.str[0:index]
	part2 := this.str[index:]
	this.str = part1 + fmt.Sprint(data) + part2
}
//inserts string to the specified index
func (this *StringBuffer) InsertString(index int, s string) {
	part1 := this.str[0:index]
	part2 := this.str[index:]
	this.str = part1 + s + part2
}
//Deletes string from start index to end
func (this *StringBuffer) Delete(start, end int) {
	part1 := this.str[0:start]
	part2 := this.str[end:]
	this.str = part1 + part2
}
//Deletes the remaining string from index start
func (this *StringBuffer) DeleteTillEnd(start int) {
	this.str = this.str[0:start]
}
//checks if the string content begins with s
func (this *StringBuffer) HasPrefix(s string) bool {
	return strings.HasPrefix(this.str, s)
}
//checks if the string content ends with s
func (this *StringBuffer) HasSuffix(s string) bool {
	return strings.HasSuffix(this.str, s)
}
//Clears the string content
func (this *StringBuffer) Clear() {
	this.str = ""
}
//Returns the index of the first occurence of a particular string
func (this *StringBuffer) Index(s string) int {
	return strings.Index(this.str, s)
}
//Returns the index of the last occurence of a particular string
func (this *StringBuffer) LastIndex(s string) int {
	return strings.LastIndex(this.str, s)
}
//Returns the length of the string content
func (this *StringBuffer) Len() int {
	return len(this.str)
}
//Replace the string at the specified index range with s
func (this *StringBuffer) Replace(start, end int, s string) {
	part1 := this.str[0:start]
	part2 := this.str[end:]
	this.str = part1 + s + part2
}
//Reverse the string content
func (this *StringBuffer) Reverse() {
	s := ""
	for i := len(this.str) - 1; i >= 0; i-- {
		s += string(this.str[i])
	}
	this.str = s
}
//Returns the substring from start to end index
func (this *StringBuffer) Substring(start, end int) string {
	return this.str[start:end]
}
//Returns the remaining string from the start index
func (this *StringBuffer) SubTillEnd(start int) string {
	return this.str[start:]
}

//StringTokenizer type
type StringTokens struct {
	str          string
	back         string
	delimiters   string
	returnDelims bool
	separator    string
	buffer       *StringBuffer
}
//Creates an instance of StringTokens with string s and the default delimeter being a white space
func NewStrTokens(s string) *StringTokens {
	sTokens := new(StringTokens)
	sTokens.str = s
	sTokens.back = s
	sTokens.delimiters = " "
	sTokens.returnDelims = false
	sTokens.init()
	return sTokens
}
//Creates an instance of StringTokens with delimiters and bool whether to return delimiters as tokens
func NewStrTokensDelims(s, delimiters string, returnDelims bool) *StringTokens {
	sTokens := new(StringTokens)
	sTokens.str = s
	sTokens.back = s
	sTokens.delimiters = delimiters
	sTokens.returnDelims = returnDelims
	sTokens.init()
	return sTokens
}
//initializes the new StringTokens
func (this *StringTokens) init() {
	this.separator = "[<{()}>]"
	for i := 0; i < len(this.delimiters); i++ {
		var tmp []string
		var d string
		if this.delimiters == " " {
			tmp = strings.Fields(this.str)
			this.buffer = NewStringBuffer(strings.Join(tmp, this.separator))
			return
		} else {
			d = string(this.delimiters[i])
			tmp = strings.Split(this.str, d, 0)
		}
		tmpStr := ""
		for j, s := range tmp {
			if strings.HasSuffix(s, this.separator) {
				tmpStr += s + d + this.separator
			} else {
				tmpStr += s + this.separator + d + func() string {
					if j == len(tmp)-1 {
						return this.separator
					} else {
						if strings.HasPrefix(tmp[j+1], this.separator) {
							return ""
						}
					}
					return this.separator
				}()
			}
			this.str = tmpStr[0 : len(tmpStr)-len(this.separator)-1]
		}
	}
	this.buffer = NewStringBuffer(this.str)
}
//Counts the tokens remaining
func (this *StringTokens) CountTokens() int {
	tmp := new(StringTokens)
	tmp.str = this.back
	tmp.delimiters = this.delimiters
	tmp.returnDelims = this.returnDelims
	tmp.init()
	tokens := 0
	for tmp.HasMoreTokens() {
		tmp.NextToken()
		tokens++
	}
	return tokens
}
//Checks if there are more tokens
func (this *StringTokens) HasMoreTokens() bool {
	if this.buffer.Index(this.separator) != -1 {
		return this.buffer.String() != this.separator
	} else {
		if this.buffer.Len() > 0 {
			return true
		}
		return false
	}
	return false
}
//Fetches the next token, returns the token or empty string
func (this *StringTokens) NextToken() string {
	if !this.HasMoreTokens() {
		return ""
	} else {
		index := this.buffer.Index(this.separator)
		if index == -1 {
			token := this.buffer.String()
			this.buffer.Clear()
			return token
		}
		l := len(this.separator)
		token := this.buffer.Substring(0, index)
		this.buffer.Delete(0, index+l)
		if index == 0 || (strings.Index(this.delimiters, token) != -1 && !this.returnDelims) {
			return this.NextToken()
		}
		return token
	}
	return ""
}
//Fetches the next token after delimeter, returns the token or empty string
func (this *StringTokens) NextTokenAfterDelim(delim string) string {
	index := this.buffer.Index(delim)
	if index == -1 {
		return ""
	}
	this.buffer.Delete(0, index+len(delim))
	return this.NextToken()
}
